/**
 * @file key.c
 * @author chushang (email:chushangcs@163.com)
 * @brief 按键头文件，实现按键单击，双击，长按，持续长按，按下瞬间，释放瞬间，持续按住的操作检测
 *        如有错误，欢迎联系邮箱，感激不尽！
 *
 * @version 1.3
 * @date 2025-06-03
 *
 * @copyright Copyright (c) 2025
 *
 * @note 使用时请使用KeyConfig初始化Key
 */

#include "key.h"
#include "stdint.h"
#include "stdlib.h"

static Key     key_instance[KEY_MAX_NUM];
static uint8_t key_idx = 0; // 按键实例索引

/**
 * @brief 创建按键实例
 *
 * @param kc [in] 按键配置结构体
 * @return 按键实例指针
 */
Key *Key_Register(KeyConfig *kc)
{
    if (kc == NULL)
    {
        return NULL;
    }

    if (key_idx >= KEY_MAX_NUM)
    {
        return NULL;
    }
    Key *k                   = &key_instance[key_idx++];
    k->gpio_pin              = kc->gpio_pin;
    k->gpio_port             = kc->gpio_port;
    k->long_min_time         = kc->long_min_time;
    k->ReadPinState          = kc->ReadPinState;
    k->period                = kc->period;
    k->double_click_interval = kc->double_click_interval;

    return k;
}

// Key_Init()已被弃用
// /**
//  * @brief 初始化按键实例
//  * @note 该函数只负责初始化，不负责分配内存
//  *
//  * @param _k [out] 需要被初始化的按键实例
//  * @param _kc [in] 按键初始化配置结构体
//  */
// void Key_Init(Key *_k, KeyConfig *_kc)
// {
//     if (key_idx >= KEY_MAX_NUM)
//     {
//         return;
//     }
//     memset(_k, 0, sizeof(Key));
//     _k->gpio_pin = _kc->gpio_pin;
//     _k->gpio_port = _kc->gpio_port;
//     _k->long_min_time = _kc->long_min_time;
//     _k->ReadPinState = _kc->ReadPinState;
//     _k->period = _kc->period;
//     _k->double_click_interval = _kc->double_click_interval;
// }

/**
 * @brief 按键扫描函数，实现按键k的扫描
 *
 * @param k [in] 按键实例
 *
 * @note 请确保该函数被周期调用，周期建议同Key结构体的period成员一致，建议20ms
 */
static void Key_Scan(Key *k)
{
    if (k == NULL)
    {
        return;
    }

    k->prev_state    = k->curr_state;      // 前一时刻电平状态
    k->curr_state    = k->ReadPinState(k); // 当前时刻电平状态
    KeyPinState prev = k->prev_state;      // 取别名
    KeyPinState curr = k->curr_state;      // 取别名

    if (prev == KEY_PRESSED && curr == KEY_PRESSED) // 按键按住
    {
        k->state |= KEY_HOLD;
    }
    else // 按键没有按住
    {
        k->state &= ~KEY_HOLD;
    }

    if (prev == KEY_RELEASE && curr == KEY_PRESSED)
    {
        k->state |= KEY_DOWN; // 按下瞬间
    }

    if (prev == KEY_PRESSED && curr == KEY_RELEASE)
    {
        k->state |= KEY_UP; // 释放瞬间
    }

    switch (k->mode)
    {
    case 0: // 等待首次按下
        if (prev == KEY_RELEASE && curr == KEY_PRESSED)
        {
            // 首次按下
            k->pressing_time = 0;
            k->mode          = 1; // 处理长按或首次释放
        }
        break;

    case 1: // 处理长按或首次释放
        k->pressing_time += k->period;
        if (k->pressing_time >= k->long_min_time)
        {
            k->state |= KEY_LONG; // 长按
            k->mode = 4;          // 检查是否长按不放
        }
        else if (prev == KEY_PRESSED && curr == KEY_RELEASE)
        {
            k->pressing_time = 0;
            k->mode          = 2; // 首次释放，可能为单击或双击，需进入双击等待窗口
        }
        break;

    case 2: // 双击等待窗口
        k->pressing_time += k->period;
        if (k->pressing_time >= k->double_click_interval)
        {
            k->state |= KEY_CLICK; // 超过双击判定间隔，视为触发单击
            k->mode = 0;
        }
        else if (prev == KEY_RELEASE && curr == KEY_PRESSED) // 检测到第二次按下
        {
            k->pressing_time = 0;
            k->mode          = 3; // 检测第二次释放
        }
        break;

    case 3: // 检测第二次释放
        k->pressing_time += k->period;
        /* 这里添加(k->pressing_time >= k->long_min_time)的判断是因为如果不加该判断
           那么每次双击判定都需要等待第二次按下再释放才会判定成功，加了该判断的话如果第二次按下
           一直不松开的话，按下的时长超过了long_min_time也会判定为双击。
           如果你需要更短的判定时长，请将k->long_min_time修改成任意想要的数字即可，比如k->pressing_time >= 200 */
        if ((prev == KEY_PRESSED && curr == KEY_RELEASE) || (k->pressing_time >= k->long_min_time))
        {
            k->state |= KEY_DOUBLE; // 第二次释放视为触发双击
            k->mode = 0;
        }
        // else
        // {
        //     k->pressing_time += k->period;
        //     if (k->pressing_time >= k->long_min_time)
        //     {
        //         k->state |= KEY_LONG; // 第二次长按
        //         k->mode = 0;
        //     }
        // }
        break;

    case 4:                                             // 检查是否长按不放
        if (prev == KEY_PRESSED && curr == KEY_RELEASE) // 长按释放
        {
            k->mode = 0;
        }
        else if (curr == KEY_PRESSED)
        {
            k->state |= KEY_REPEAT; // 长按按住不放
            k->mode = 4;            // 持续检测
        }
        break;
    }
}

/**
 * @brief 按键周期任务函数，需确保该函数被周期调用，建议放在定时器中断里
 *
 */
void Key_Tick(void)
{
    for (uint8_t i = 0; i < key_idx; i++)
    {
        Key_Scan(&key_instance[i]);
    }
}

/**
 * @brief 读取按键k的引脚电平状态
 *
 * @param  k [in] 指向按键的指针
 * @return 按键被按下，返回KEY_PRESSED，否则返回KEY_RELEASE
 */
KeyPinState Key_ReadPin(Key *k)
{
    if (k == NULL)
    {
        return KEY_RELEASE;
    }

    if (k->ReadPinState(k->gpio_port, k->gpio_pin) == 0)
    {
        return KEY_PRESSED;
    }
    else
    {
        return KEY_RELEASE;
    }
}

/**
 * @brief 检查按键k是否存在ks状态
 *
 * @param k [in] 按键k
 * @param ks [in] 待检查的状态
 * @return 1：按键k存在该状态
 *         0：按键k不存在该状态
 */
uint8_t Key_Check(Key *k, KeyState ks)
{
    if (k == NULL)
    {
        return 0;
    }

    if (k->state & ks)
    {
        if (ks != KEY_HOLD) // KEY_HOLD不需要手动清零，硬件自动清零
        {
            k->state &= ~ks; // 清零对应的状态标志
        }
        return 1;
    }
    return 0;
}

/**
 * @brief 清空按键状态
 *
 * @param k [in] 按键实例指针
 */
void Key_Clear(Key *k)
{
    if (k != NULL)
        k->state = KEY_NOACTION;
}
